#include "translate_utils.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_CODE_LEN 1024

InterCode* codes[MAX_CODE_LEN];

int codeCnt;

int tempCnt = 1;

int labelCnt = 1;

FILE* out;

Operand* operands[MAX_CODE_LEN];

int newTemp() {
    return tempCnt++;
}

int newLabel() {
    return labelCnt++;
}

InterCode* genCode() {
    return malloc(sizeof(InterCode));
}

Operand* genOperand(int kind, int attr) {
    Operand* opt = malloc(sizeof(Operand));
    switch (kind) {
        case OPERAND_VARIABLE :
            opt->kind = VARIABLE;
            opt->attr.var_no = attr;
            break;
        case OPERAND_CONSTANT :
            opt->kind = CONSTANT;
            opt->attr.value = attr;
            break;
        case OPERAND_ADDRESS :
            opt->kind = ADDRESS;
            opt->attr.var_no - attr;
            break;
    }
    return opt;
}

Operand* getOperand(int kind, int no) {
    if (operands[no] == NULL) {
        operands[no] = genOperand(kind, no);
        operands[no]->kind = kind;
        operands[no]->attr.var_no = no;
    }
    return operands[no];
}

InterCode* genAssignCode(Operand* opt1, Operand* opt2) {
    InterCode* code = genCode();
    code->kind = ASSIGN;
    code->attr.assign.left = opt1;
    code->attr.assign.right = opt2;
    return code;
}

InterCode* genOperationCode(Operand* result, Operand* variable1, Operand* variable2, int type) {
    InterCode* code = genCode();
    switch (type) {
        case OPERATION_ADD : code->kind = ADD; break;
        case OPERATION_SUB : code->kind = SUB; break;
        case OPERATION_MUL : code->kind = MUL; break;
        case OPERATION_DIV : code->kind = DIV; break;
    }
    code->attr.calc.result = result;
    code->attr.calc.opt1 = variable1;
    code->attr.calc.opt2 = variable2;
    return code;
}

InterCode* genMinusOperationCode(Operand* result, Operand* zero, Operand* variable) {
    InterCode* code = genCode();
    code->kind = SUB;
    code->attr.calc.result = result;
    code->attr.calc.opt1 = zero;
    code->attr.calc.opt2 = variable;
    return code;
}

InterCode* genReturnCode(Operand* retval) {
    InterCode* code = genCode();
    code->kind = RET;
    code->attr.ret.retval = retval;
    return code;
}

InterCode* genIFJumpCode(Operand* variable1, Operand* variable2, char* opt, int labelTrue, int labelFalse) {
    InterCode* code = genCode();
    code->kind = IFJUMP;
    code->attr.ifjump.var1 = variable1;
    code->attr.ifjump.var2 = variable2;
    code->attr.ifjump.opt = opt;
    code->attr.ifjump.labelTrue = labelTrue;
    code->attr.ifjump.laeblFalse = labelFalse;
    return code;
}

InterCode* genGotoLabelCode(int label) {
    InterCode* code = genCode();
    code->kind = GLABEL;
    code->attr.label.label = label;
    return code;
}

InterCode* genLabelCode(int label) {
    InterCode* code = genCode();
    code->kind = SLABEL;
    code->attr.label.label = label;
    return code;
}

InterCode* genIFNotNullJumpCode(Operand* var, Operand* null, int labelTrue) {
    InterCode* code = genCode();
    code->kind = IFJUMP;
    code->attr.ifjump.var1 = var;
    code->attr.ifjump.var2 = null;
    code->attr.ifjump.opt = malloc(sizeof(char) * strlen("!="));
    strcpy(code->attr.ifjump.opt, "!=");
    code->attr.ifjump.labelTrue = labelTrue;
    return code;
}

InterCode* genCallFunctionCode(Operand* variabel, char* funcName) {
    InterCode* code = genCode();
    code->kind = FUNCTION_CALL;
    code->attr.cfunc.var = variabel;
    code->attr.cfunc.funcName = funcName;
    return code;
}

InterCode* genFunctionArgumentCode(Operand* argument) {
    InterCode* code = genCode();
    code->kind = ARGUMENT;
    code->attr.farg.arg = argument;
    return code;
}

InterCode* genFunctionLabelCode(char* name) {
    InterCode* code = genCode();
    code->kind = FLABEL;
    code->attr.lfunc.funcName = name;
    return code;
}

InterCode* genFunctionParamCode(Operand* parameter) {
    InterCode* code = genCode();
    code->kind = PARAM;
    code->attr.fparam.param = parameter;
    return code;
}

InterCode* genArrayDeclarationCode(Operand* array, int size) {
    InterCode* code = malloc(sizeof(InterCode));
    code->kind = ARRAY_DECLARATION;
    code->attr.darray.array = array;
    code->attr.darray.size = size;
    return code;
}

InterCode* genArrayAddressCode(Operand* address, Operand* array, int index, int offset) {
    InterCode* code = genCode();
    code->kind = ARRAY_ADDRESS;
    code->attr.aarray.address = address;
    code->attr.aarray.array = array;
    code->attr.aarray.index = index;
    code->attr.aarray.offset = offset;
    return code;
}

// interface implementation

void genConstantAssign(int var, int value) {
    Operand* variable = getOperand(OPERAND_VARIABLE, var);
    Operand* constant = genOperand(OPERAND_CONSTANT, value);
    codes[codeCnt++] = genAssignCode(variable, constant);
}

void genVariableAssign(int var1, int var2) {
    Operand* variable1 = getOperand(OPERAND_VARIABLE, var1);
    Operand* variable2 = getOperand(OPERAND_VARIABLE, var2);
    codes[codeCnt++] = genAssignCode(variable1, variable2);
}

void genVariableOperation(int res, int var1, int var2, int type) {
    Operand* result = getOperand(OPERAND_VARIABLE, res);
    Operand* variable1 = getOperand(OPERAND_VARIABLE, var1);
    Operand* variable2 = getOperand(OPERAND_VARIABLE, var2);
    codes[codeCnt++] = genOperationCode(result, variable1, variable2, type);
}

void genMinusOperation(int res, int var) {
    Operand* result = getOperand(OPERAND_VARIABLE, res);
    Operand* constant = genOperand(OPERAND_CONSTANT, 0);
    Operand* variable = getOperand(OPERAND_VARIABLE, var);
    codes[codeCnt++] = genMinusOperationCode(result, constant, variable);
}

void genFunctionReturn(int ret) {
    Operand* retval = getOperand(OPERAND_VARIABLE, ret);
    codes[codeCnt++] = genReturnCode(retval);
}

void genIFJump(int var1, int var2, char* opt, int labelTrue, int labelFalse) {
    Operand* variable1 = getOperand(OPERAND_VARIABLE, var1);
    Operand* variable2 = getOperand(OPERAND_VARIABLE, var2);
    codes[codeCnt++] = genIFJumpCode(variable1, variable2, opt, labelTrue, labelFalse);
}

void genGotoLabel(int label) {
    codes[codeCnt++] = genGotoLabelCode(label);
}

void genLabel(int label) {
    codes[codeCnt++] = genLabelCode(label);
}

void genIFNotNullJump(int var, int labelTrue) {
    Operand* variable = getOperand(OPERAND_VARIABLE, var);
    Operand* null = genOperand(OPERAND_CONSTANT, 0);
    codes[codeCnt++] = genIFNotNullJumpCode(variable, null, labelTrue);
}

void genCallFunction(int var, char* funcName) {
    Operand* variable = getOperand(OPERAND_VARIABLE, var);
    codes[codeCnt++] = genCallFunctionCode(variable, funcName);
}

void genFunctionArgument(int arg) {
    Operand* argument = getOperand(OPERAND_VARIABLE, arg);
    codes[codeCnt++] = genFunctionArgumentCode(argument);
}

void genFunctionLabel(char* funcName) {
    codes[codeCnt++] = genFunctionLabelCode(funcName);
}

void genFunctionParam(int param) {
    Operand* parameter = getOperand(OPERAND_VARIABLE, param);
    codes[codeCnt++] = genFunctionParamCode(parameter);
}

void genArrayDeclaration(int arr,int size) {
    Operand* array = getOperand(OPERAND_ADDRESS, arr);
    codes[codeCnt++] = genArrayDeclarationCode(array, size);
}

void genArrayAddress(int addr, int arrno, int index, int offset) {
    Operand* address = getOperand(OPERAND_ADDRESS, addr);
    Operand* array = getOperand(OPERAND_ADDRESS, arrno);
    codes[codeCnt++] = genArrayAddressCode(address, array, index, offset);
}


/* printer */

void printOperand(Operand* operand) {
    switch (operand->kind) {
        case VARIABLE :
            fprintf(out, "t%d", operand->attr.var_no);
            break;
        case CONSTANT :
            fprintf(out, "#%d", operand->attr.value);
            break;
        case ADDRESS :
            fprintf(out, "*t%d", operand->attr.var_no);
            break;
        default :
            break;
    }
}

void printAssign(Operand* left, Operand* right) {
    printOperand(left);
    fprintf(out, "%s", " := ");
    printOperand(right);
    // printf("t%d := ", left->attr.var_no);
    // switch (right->kind) {
    //     case VARIABLE :
    //         printf("t%d", right->attr.var_no);
    //         break;
    //     case CONSTANT :
    //         printf("#%d", right->attr.value);
    //         break;
    //     case ADDRESS :
    //         printf("&%d")
    // }
    fprintf(out, "%s", "\n");
}

void printOperation(Operand* result, Operand* opt1, Operand* opt2, char type) {
    // printf("t%d := ", result->attr.var_no);
    printOperand(result);
    fprintf(out, "%s", " := ");
    printOperand(opt1);
    switch (type) {
        case ADD :
            // printf("t%d + t%d", opt1->attr.var_no, opt2->attr.var_no);
            fprintf(out, "%s", " + ");
            break;
        case SUB :
            // if (!opt1->attr.var_no) {
            //     printf("#0 - t%d", opt2->attr.var_no);
            // } else {
            //     printf("t%d - t%d", opt1->attr.var_no, opt2->attr.var_no);
            // }
            fprintf(out, "%s", " - ");
            break;
        case MUL :
            // printf("t%d * t%d", opt1->attr.var_no, opt2->attr.var_no);
            fprintf(out, "%s", " * ");
            break;
        case DIV :
            // printf("t%d / t%d", opt1->attr.var_no, opt2->attr.var_no);
            fprintf(out, "%s", " / ");
            break;
    }
    printOperand(opt2);
    fprintf(out, "%s", "\n");
}

void printReturn(Operand* retval) {
    fprintf(out, "RETURN t%d\n", retval->attr.var_no);
}

void printJump(Operand* var1, Operand* var2, char* opt, int labelTrue, int labelFalse) {
    if (var2->attr.var_no != 0) {
        fprintf(out, "IF t%d %s t%d GOTO Label%d\n", var1->attr.var_no, opt, var2->attr.var_no, labelTrue);
    } else {
        fprintf(out, "IF t%d %s #%d GOTO Label%d\n", var1->attr.var_no, opt, var2->attr.value, labelTrue);
    }
}

void printGotoLabel(int label) {
    fprintf(out, "GOTO label%d\n", label);
}

void printLabel(int label) {
    fprintf(out, "LABEL label%d\n", label);
}

void printCallFunction(Operand* variable, char* funcName) {
    if (!strcmp(funcName, "read")) {
        fprintf(out, "READ t%d\n", variable->attr.var_no);
    } else if (!strcmp(funcName, "write")) {
        fprintf(out, "WRITE t%d\n", variable->attr.var_no);
    } else if (variable->attr.var_no == -1) {
        fprintf(out, "CALL %s\n", funcName);
    } else {
        fprintf(out, "t%d := CALL %s\n", variable->attr.var_no, funcName);
    }
}

void printArgument(Operand* argument) {
    fprintf(out, "ARG t%d\n", argument->attr.var_no);
}

void printFunction(char* funcName) {
    fprintf(out, "FUNCTION %s :\n", funcName);
}

void printfParam(Operand* param) {
    fprintf(out, "PARAM t%d\n", param->attr.var_no);
}

void printArrayDeclaration(Operand* array, int size) {
    fprintf(out, "DEC t%d %d\n", array->attr.var_no, size * 4);
}

void printArrayAddressCode(Operand* address, Operand* array, int index, int offset) {
    fprintf(out, "t%d := #4 * t%d\n", offset, index);
    fprintf(out, "t%d := &t%d + t%d\n", address->attr.var_no, array->attr.var_no, offset);
}

void printCode(InterCode* code) {
    switch (code->kind) {
        case ASSIGN :
            printAssign(code->attr.assign.left, code->attr.assign.right);
            break;
        case ADD :
        case SUB :
        case MUL :
        case DIV :
            printOperation(code->attr.calc.result, code->attr.calc.opt1, code->attr.calc.opt2, code->kind);
            break;
        case RET :
            printReturn(code->attr.ret.retval);
            break;
        case IFJUMP :
            printJump(code->attr.ifjump.var1, code->attr.ifjump.var2, code->attr.ifjump.opt, code->attr.ifjump.labelTrue, code->attr.ifjump.laeblFalse);
            break;
        case GLABEL :
            printGotoLabel(code->attr.label.label);
            break;
        case SLABEL :
            printLabel(code->attr.label.label);
            break;
        case FUNCTION_CALL :
            printCallFunction(code->attr.cfunc.var, code->attr.cfunc.funcName);
            break;
        case ARGUMENT :
            printArgument(code->attr.farg.arg);
            break;
        case FLABEL :
            printFunction(code->attr.lfunc.funcName);
            break;
        case PARAM :
            printfParam(code->attr.fparam.param);
            break;
        case ARRAY_DECLARATION :
            printArrayDeclaration(code->attr.darray.array, code->attr.darray.size);
            break;;
        case ARRAY_ADDRESS :
            printArrayAddressCode(code->attr.aarray.address, code->attr.aarray.array, code->attr.aarray.index, code->attr.aarray.offset);
            break;
    }
}

void printCodes(char* outf) {
    out = fopen(outf, "w");
    if (out == NULL) {
        perror("file open error\n");
    }
    int i;
    for (i = 0; i < codeCnt; i++) {
        printCode(codes[i]);
    }
}